package com.thtfpc.ssdp;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;

/**
 * <p>SimpleServiceDeclarer 类是一个利用SSDP协议在组播网络中声明SSDP设备和服务的类</p>
 * <p>
 * 该类的主要功能：
 * <p>1、注册要对外发布的服务</p>
 * <p>2、监听组播网络中的查询请求</p>
 * <p>3、判断组播请求是否和本机发布的服务匹配</p>
 * <p>4、服务匹配后，单播形式向查询设备回应服务的信息</p>
 */
public class SimpleServiceDeclarer extends SSDPChannel {
    static Logger logger = Logger.getLogger(SimpleServiceFinder.class.getSimpleName());
    private static String TAG = SimpleServiceFinder.class.getSimpleName();

    /**
     * SearchTarget 对象，存储了SSDP协议中对ST(SearchTarget)字段的解析
     * SearchTarget 的五种基本模板， 但根据实际情况，厂商都有自己实现
     * <p>ssdp:all 搜索所有设备和服务</p>
     * <p>upnp:rootdevice 仅搜索网络中的根设备</p>
     * <p>uuid:device-UUID 查询UUID标识的设备</p>
     * <p>urn:schemas-upnp-org:device:device-Type:version 查询device-Type字段指定的设备类型，设备类型和版本由UPNP组织定义。</p>
     * <p>urn:schemas-upnp-org:service:service-Type:version 查询service-Type字段指定的服务类型，服务类型和版本由UPNP组织定义。</p>
     */
    public static class SearchTarget {
        public String scheme;
        public String targetName;
        public int version;

        @Override
        public String toString() {
            return "SearchTarget{" +
                    "scheme='" + scheme + '\'' +
                    ", targetName='" + targetName + '\'' +
                    ", version=" + version +
                    '}';
        }

        public static SearchTarget parse(String scLine) {
            String scheme = null;
            String targetName = null;
            int version = 0;
            if (scLine == null) {
                return null;
            }
            else if (scLine.startsWith("urn:")) {
                String[] ps = scLine.split(":");
                if (ps.length == 5 && ("service".equals(ps[2]) || "device".equals(ps[2]))) {
                    scheme = ps[0] + ":" + ps[1] + ":" + ps[2];
                    targetName = ps[3];
                    version = Integer.parseInt(ps[4]);
                }
            }
            else if (scLine.startsWith("upnp:")) {
                String[] ps = scLine.split(":");
                targetName = ps[1];
                scheme = ps[0];
                version = 0;
            }
            else if (scLine.startsWith("uuid:device-")) {
                scheme = "uuid:device";
                targetName = scLine.split("-")[1];
                version = 0;
            }
            else if (scLine.startsWith("ssdp:")) {
                scheme = "ssdp";
                targetName = scLine.split(":")[1];
                version = 0;
            }
            if (scheme != null && targetName != null) {
                SearchTarget searchTarget = new SearchTarget();
                searchTarget.scheme = scheme;
                searchTarget.targetName = targetName;
                searchTarget.version = version;
                return searchTarget;
            }
            return null;
        }
    }

    protected Map<String, SSDPDevice> serviceRegistered;

    public SimpleServiceDeclarer() {
        super(1900, true);
        serviceRegistered = new HashMap<>();
    }

    /**
     * 当收到一个M-SEARCH组播查询信息后，被回调
     *
     * @param ssdpMessage 接收到的SSDPMessage对象，包含了查询源发出的M-SEARCH数据包的基本信息
     */
    @Override
    protected void onMessage(SSDPMessage ssdpMessage) {
        System.out.println(ssdpMessage.getHeader("ST"));
        if ("M-SEARCH * HTTP/1.1".equals(ssdpMessage.getMessageType())
                && "\"ssdp:discover\"".equals(ssdpMessage.getHeader("MAN"))) {
            String searchTargetString = ssdpMessage.getHeader("ST");
            SearchTarget searchTarget = SearchTarget.parse(searchTargetString);
            if (searchTarget != null) {
                onSearchTargetReceived(ssdpMessage, searchTarget);
            }
            else {
                System.out.println("parse SC field error.");
            }
        }
    }


    protected void onSearchTargetReceived(SSDPMessage message, SearchTarget searchTarget) {
        //SearchTarget到达后，要和本地的发布服务列表进行匹配
        synchronized (serviceRegistered) {
            String searchTargetKey = searchTarget.scheme + ":" + searchTarget.targetName + ((searchTarget.scheme.startsWith("urn:")) ? ":" + searchTarget.version : "");
            if (serviceRegistered.containsKey(searchTargetKey)) {
                onSearchRequestMatched(message, serviceRegistered.get(searchTargetKey));
            }
        }
    }

    /**
     * 当网络中有其他设备在声明寻找的服务和自身声明的某项服务匹配时，该事件方法被回调；
     * 构造一个HTTP/1.1 200 OK 的响应，使用UDP单点通讯，将本机的目标匹配服务的信息发送给查询设备
     *
     * @param ssdpMessage 存储了查询设备发出的M-SEARCH查询信息
     *                    <p>可利用ssdpMessage.getHeader来索引目标信息</p>
     *                    <p>ssdpMessage.getHeader("ST")存储了查询目标</p>
     * @param device      被匹配的本地注册服务，该服务是由device对象提供
     *                    <p>device.serverURL 字段存储了服务的url地址</p>
     */
    protected void onSearchRequestMatched(SSDPMessage ssdpMessage, SSDPDevice device) {
        //logger.log(Level.INFO, "[onServiceMatched]: " + ssdpMessage.toString());
        SSDPMessage messageResponse = new SSDPMessageBuilder()
                .messageType("HTTP/1.1 200 OK")
                .header("USN", "uuid:" + device.getId())
                .header("ST", ssdpMessage.getHeader("ST"))
                .header("Ext", "")
                .header("Cache-Control", "max-age=8000")
                .header("LOCATION", device.getServiceUrl())
                .header("SERVER", System.getProperty("os.name"))
                .header("TITLE", device.getTitle())
                .address(ssdpMessage.getAddress())
                .port(ssdpMessage.getPort())
                .build();
        sendMessage(messageResponse);
    }

    /**
     * Send a announcement of notification to the muticast address.
     * <p>
     * a typical Notify message is as follows:
     * <p>
     * NOTIFY * HTTP/1.1
     * </p>
     * <p>Host: 239.255.255.250:1900</p>
     * <p>NT: blenderassociation:blender</p>
     * <p>NTS: ssdp:alive</p>
     * <p>USN: someunique:idscheme3</p>
     * <p>AL: http://xxx/xxx</p>
     * <p>Cache-Control: max-age = 7393</p>
     * </p>
     */
    protected void sendNotify() {
        if (isRunning()) {
            synchronized (serviceRegistered) {
                Iterator<SSDPDevice> iterator = serviceRegistered.values().iterator();
                while (iterator.hasNext()) {
                    SSDPDevice device = iterator.next();
                    SSDPMessage ssdpMessage = new SSDPMessageBuilder()
                            .messageType("NOTIFY * HTTP/1.1")
                            .header("HOST", SSDPChannel.MULTICAST_ADDRESS + ":" + SSDPChannel.MULTICAST_PORT)
                            .header("NTS", "ssdp:alive")
                            .header("NT", device.getName())
                            .header("USN", device.getId())
                            .header("AL", device.getServiceUrl())
                            .header("Cache-Control", "max-age=8000")
                            .address(SSDPChannel.MULTICAST_ADDRESS)
                            .port(SSDPChannel.MULTICAST_PORT)
                            .build();
                    sendMessage(ssdpMessage);
                }
            }
        }
    }


    @Override
    protected void onStopping() {
        super.onStopping();
        synchronized (serviceRegistered) {
            Iterator<SSDPDevice> iterator = serviceRegistered.values().iterator();
            while (iterator.hasNext()) {
                SSDPDevice device = iterator.next();
                SSDPMessage ssdpMessage = new SSDPMessageBuilder()
                        .messageType("NOTIFY * HTTP/1.1")
                        .header("HOST", SSDPChannel.MULTICAST_ADDRESS + ":" + SSDPChannel.MULTICAST_PORT)
                        .header("NTS", "ssdp:byebye")
                        .header("NT", device.getName())
                        .header("USN", device.getId())
                        .address(SSDPChannel.MULTICAST_ADDRESS)
                        .port(SSDPChannel.MULTICAST_PORT)
                        .build();
                sendMessage(ssdpMessage);
            }
        }
    }

    /**
     * register a service on ssdp declarer.
     * when declare start, all the multicast message will to be match declarers's device.
     *
     * @param ssdpDevice define the service to declarer in the ssdp network.
     *                   <p><b>deviceId</b> define the USN  field.</p>
     *                   <p><b>name</b> define the ST field, such as upnp.</p>
     *                   <p><b>serverURL</b> define the service url.</p>
     *                   <p><b>title</b> define the service title.</p>
     */
    public void registerService(SSDPDevice ssdpDevice) {
        synchronized (serviceRegistered) {
            if (serviceRegistered.containsKey(ssdpDevice.getName()) == false) {
                serviceRegistered.put(ssdpDevice.getName(), ssdpDevice);
            }
        }
    }


    /**
     * unregister a service on ssdp declarer.
     *
     * @param serviceName remove the ssdp service with the service' name.
     */
    public void unregisterService(String serviceName) {
        synchronized (serviceRegistered) {
            if (serviceRegistered.containsKey(serviceName)) {
                serviceRegistered.remove(serviceName);
            }
        }
    }
}
